//旧的 直接引进抄袭别的包 export * from "touchable-hook";

/** 可点按的区域Div; 手机电脑都支持。
 * The state machine used here is based on the one provided
 * in react-native-web:
 *
 * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/Touchable/index.js
 */

import * as React from "react";
import { isHoverEnabled } from "./hover-enabled";
//类型ResponderEvent自动添加进来的
import {ResponderEvent, useGestureResponder} from "react-gesture-responder";
//自动添加进来的，Partial<> 代表一部分可选的参数对象集合。
import {Partial} from "rollup-plugin-typescript2/dist/partial";

/**
 * useTouchable
 *
 * useTouchable is a hook that attempt to emulate native touch behaviour for things
 * like list items, buttons, etc.
 *
 * const { bind, active } = useTouchable({
 *   onPress: () => console.log('hello'),
 *   disabled: false,
 *   delay: 120
 * })
 */


const HIGHLIGHT_DELAY_MS = 30;          //默认30毫秒 才显示反映被点按。
const PRESS_EXPAND_PX = 20;            //区域默认点按后的放大部分尺寸 : px;
const LONG_PRESS_DELAY = 700 - HIGHLIGHT_DELAY_MS;           //默认点按时间超过算为长按的。

//初始化或者完全清空的： state.current === "NOT_RESPONDER"
//状态有点太多了？
type States =
    | "ERROR"
    | "NOT_RESPONDER"
    | "RESPONDER_ACTIVE_IN"
    | "RESPONDER_ACTIVE_OUT"
    | "RESPONDER_PRESSED_IN"
    | "RESPONDER_PRESSED_OUT"
    | "RESPONDER_LONG_PRESSED_IN";

//触摸行为开始的=RESPONDER_GRANT；
//滚动掉了或者屏蔽掉了=RESPONDER_TERMINATED
//触摸随时可能取消的意思(不一定触发按钮回调执行函数)=RESPONDER_RELEASE；
//矩形区域进出=ENTER_PRESS_RECT
//把旧版本的 事件"DELAY" 改名为 "PRESS_DETECTED" 表示算入延迟后的已经点按行为是成立了。

//点击按到的： 派生出来的什么离开 又进去事件，真的有必要吗?
type Events =
    | "RESPONDER_GRANT"
    | "PRESS_DETECTED"
    | "LONG_PRESS_DETECTED"
    | "LEAVE_PRESS_RECT"
    | "ENTER_PRESS_RECT"
    | "RESPONDER_RELEASE"
    | "RESPONDER_TERMINATED";


type TransitionsType = { [key in States]: TransitionType };

type TransitionType = { [key in Events]: States };

//状态+事件：矩阵：  NOT_RESPONDER 意思： 没事情无效果。
//状态有点多余： RESPONDER_ACTIVE_OUT ，RESPONDER_PRESSED_OUT ：都离开了跑到矩形框框之外的也要跟踪触发？ 还不如直接作废。
//直接利用旧的 状态转移的矩阵：
const transitions = {
    NOT_RESPONDER: {
        PRESS_DETECTED: "NOT_RESPONDER",
        RESPONDER_GRANT: "RESPONDER_ACTIVE_IN",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "NOT_RESPONDER",
        LEAVE_PRESS_RECT: "NOT_RESPONDER",
        LONG_PRESS_DETECTED: "NOT_RESPONDER"
    },
    RESPONDER_ACTIVE_IN: {
        PRESS_DETECTED: "RESPONDER_PRESSED_IN",
        RESPONDER_GRANT: "ERROR",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "RESPONDER_ACTIVE_IN",
        LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT",
        LONG_PRESS_DETECTED: "ERROR"
    },
    RESPONDER_ACTIVE_OUT: {
        PRESS_DETECTED: "RESPONDER_PRESSED_OUT",
        RESPONDER_GRANT: "ERROR",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "RESPONDER_ACTIVE_IN",
        LEAVE_PRESS_RECT: "RESPONDER_ACTIVE_OUT",
        LONG_PRESS_DETECTED: "ERROR"
    },
    RESPONDER_PRESSED_IN: {
        PRESS_DETECTED: "ERROR",
        RESPONDER_GRANT: "ERROR",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
        LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT",
        LONG_PRESS_DETECTED: "RESPONDER_LONG_PRESSED_IN"
    },
    RESPONDER_PRESSED_OUT: {
        PRESS_DETECTED: "ERROR",
        RESPONDER_GRANT: "ERROR",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
        LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT",
        LONG_PRESS_DETECTED: "ERROR"
    },
    RESPONDER_LONG_PRESSED_IN: {
        PRESS_DETECTED: "ERROR",
        RESPONDER_GRANT: "ERROR",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "RESPONDER_PRESSED_IN",
        LEAVE_PRESS_RECT: "RESPONDER_PRESSED_OUT",
        LONG_PRESS_DETECTED: "RESPONDER_LONG_PRESSED_IN"
    },
    ERROR: {
        PRESS_DETECTED: "NOT_RESPONDER",
        RESPONDER_GRANT: "RESPONDER_ACTIVE_IN",
        RESPONDER_RELEASE: "NOT_RESPONDER",
        RESPONDER_TERMINATED: "NOT_RESPONDER",
        ENTER_PRESS_RECT: "NOT_RESPONDER",
        LEAVE_PRESS_RECT: "NOT_RESPONDER",
        LONG_PRESS_DETECTED: "NOT_RESPONDER"
    }
} as TransitionsType;



type OnPressFunction = (
    e?: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event
) => void;

export interface TouchableOptions {
    delay: number;
    longPressDelay: number;
    pressExpandPx: number;
    behavior: "button" | "link";
    disabled: boolean;
    //缺省 true  滚动就是失效了;
    terminateOnScroll: boolean;
    onPress?: OnPressFunction;
    onLongPress?: OnPressFunction;
    //不支持触摸后移动的还可以触发！movable=false;
    movable?: boolean;
}

//terminateOnScroll 缺省值可以取true 这下就不会卸载组件就报错了。

const defaultOptions: TouchableOptions = {
    delay: HIGHLIGHT_DELAY_MS,
    pressExpandPx: PRESS_EXPAND_PX,
    longPressDelay: LONG_PRESS_DELAY,
    behavior: "button",
    disabled: false,
    terminateOnScroll: true,
    onPress: undefined,
    onLongPress: undefined,
    movable: false
};

/**表示某一个小div区域可以按下点击 触发函数。 返回bind? 同一个名字onXXX()可能冲突;
 * 点按 不是拖拽拉动的，相对位置是静止的，按住时间长短？才算，立刻反馈被您点到了。
 * Hook：原版乃是touchable-hook这个包的。
 * @param options
 * 本函数返回的类型声明的近20个参数对象实际是自动生成的代码，非人工编辑的。
 * 这里ListItem MenuItem IconButton Button 这四个都直接用到useTouchable的 点按触发。
 * 【淘汰中】？ 关键# 这个用了"react-gesture-responder"
 * #计划"react-gesture-responder"删除！！： #把 ListItem MenuItem Button这几个用useTouchable全都改掉？ 或useTouchable直接改？
 * @消除依赖 react-gesture-responder 参考https://use-gesture.netlify.app/docs/examples/
 */

export function useTouchable(options: Partial<TouchableOptions> = {}):
{    //本Hook返回
    hover: boolean;
    bind: {
        onTouchStart: (e: ResponderEvent) => void;
        onKeyDown: (e: React.KeyboardEvent) => void;
        onKeyUp: (e: React.KeyboardEvent) => void;
        ref: React.MutableRefObject<any>;
        onTouchMove: (e: ResponderEvent) => void;
        onTouchEnd: (e: ResponderEvent) => void;
        onTouchMoveCapture: (e: ResponderEvent) => void;
        onMouseEnter: () => void;
        onMouseLeave: () => void;
        onMouseDown?: ((e: React.MouseEvent<Element, MouseEvent>) => void) | undefined;
        onTouchStartCapture: (e: ResponderEvent) => void;
        onMouseDownCapture?: ((e: React.MouseEvent<Element, MouseEvent>) => void) | undefined
    };
    active: boolean
} {
    const {
        onPress,
        onLongPress,
        longPressDelay,
        terminateOnScroll,
        delay,
        behavior,
        disabled,
        movable
    } = {
        ...defaultOptions,
        ...options
    };
    //依赖ref绑定到Div； 底层 useGestureResponder｛Config{uid:注意唯一性id}｝。
    const ref = React.useRef<HTMLAnchorElement | HTMLDivElement | any>(null);
    const delayTimer = React.useRef<number>();
    const longDelayTimer = React.useRef<number>();
    const bounds = React.useRef<ClientRect>();
    //这两个完全不是一个含义， hover是鼠标点位置进 出来。     #当前有显示内容了。
    const [hover, setHover] = React.useState(false);
    //开始触摸的反馈showHover正常都是true,触摸点击的才会，相反掉。     #上级的允许开关
    //旧版本的 const [showHover, setShowHover] = React.useState(true);

    //active表示被触摸选中了。
    const [active, setActive] = React.useState(false);

    //直接用React.useReducer替换旧的机制（useRef<States>("NOT_RESPONDER"), function dispatch(event: Events) {）
    //原先是nextState = transitions[state.current][event];  state.current = nextState
    //如今[{ responder }, dispatch] = React.useReducer( ；这里{ responder }, 实际代表state[旧代码是state.current];  dispatch({type:'Event',addParms:{} });
    //依据事件来：旧状态切换新状态, useReducer里面其实也允许直接执行触发函数: 而这里是onEnd()回调干的:useGestureResponder{}配置钩子。
    const [{ responder }, dispatch] = React.useReducer( (state, action) => {
        const newResponder = transitions[state.responder][action.type];
        if(newResponder === "RESPONDER_PRESSED_IN" || newResponder === "RESPONDER_LONG_PRESSED_IN" ) {
            setActive(true);        //实际触发函数照理应该等到 active==true 的状态才能说是允许触发调用emitPress{内部实际执行onPress()}的吗？
        } else {
            //状态机事件ERROR的说明组件可能已经卸载了！
            //首先 RESPONDER_RELEASE: 后面又来了 RESPONDER_TERMINATED:
            //if(newResponder!=="ERROR" || (action.type !=="RESPONDER_RELEASE" && action.type !=="RESPONDER_TERMINATED") )
            setActive(false);
        }

        if (newResponder === "NOT_RESPONDER") {
            clearTimeout(delayTimer.current);
            clearTimeout(longDelayTimer.current);
        }
        //默认的：
        switch (action.type as Events) {
            default:
                return {
                    ...state,
                    responder: newResponder
                };
        }
    }, {
        responder: "NOT_RESPONDER" as States,
    });


    //旧代码 useRef<States>方式
    //const state = React.useRef<States>("NOT_RESPONDER");
    /*
     Transition from one state to another
         * @param event dispatch({ type: 'increment', someNews: { } });
    function dispatch_old(event: Events) {
        const nextState = transitions[state.current][event];
        state.current = nextState;

        if (
            nextState === "RESPONDER_PRESSED_IN" ||
            nextState === "RESPONDER_LONG_PRESSED_IN"
        ) {
            setActive(true);
        } else {
            //状态机事件触发前后都是"NOT_RESPONDER"的说明组件可能已经卸载了！
            //首先 RESPONDER_RELEASE: 后面又来了 RESPONDER_TERMINATED:
            if(state.current!=="ERROR" || (event !=="RESPONDER_RELEASE" && event !=="RESPONDER_TERMINATED") )
                setActive(false);
        }

        if (nextState === "NOT_RESPONDER") {
            clearTimeout(delayTimer.current);
            clearTimeout(longDelayTimer.current);
        }
    }
    */

    function bindScroll() {
        if (terminateOnScroll) {
            document.addEventListener("scroll", onScroll, true);
        }
    }

    function unbindScroll() {
        //addEventListener() 添加的事件处理程序只能使用 removeEventListener() 来移除；移除时传入的参数与添加处理程序时使用的参数相同。这也就意味着通过 addEventListener() 添加的匿名函数将无法移除。
        //第三个参数useCapture：true，即事件捕获阶段会触发事件, 属性为false，即事件冒泡阶段才会触发, 捕获阶段是大的首先执行，后面才进入了冒泡阶段，相反div顺序触发。
        document.removeEventListener("scroll", onScroll, true);
    }
    /** 整个文档区域的 滚动了：
     * Scrolling cancels all responder events. This enables
     * the user to scroll without selecting something
     */
    function onScroll() {
        unbindScroll();
        /*组件已经卸载？
        if (responder === "ERROR") {
            return;
        }*/
        dispatch({ type: "RESPONDER_TERMINATED" });
    }

    //【核心】 create a pan responder to handle mouse / touch gestures，下面设置的onGrant：{()=>{}}才是状态机的起点。
    //实际useGestureResponder不仅支什么点击触摸按住的。我这里useTouchable只需要认定是否按住点到；我这和移动拉动拖走的事件没有关系啊。
    //使用默认的 事件冒泡模式一组接口，而不是xxxCapture?: 那一组事件捕获模式的接口。 useGestureResponder针对两种策略模式要区分对待配置接口的名称。
    const { bind, terminateCurrentResponder } = useGestureResponder({
        onStartShouldSet: () => true,
        onGrant: (_state, e) => {
            //鼠标模式isHoverEnabled的：不需要延迟触发。
            onStart(e,isHoverEnabled() ? 0 : undefined);
        },
        onRelease: (_state, e) => onEnd(e),
        onMove: (_state, e) => onTouchMove(e),
        onTerminate: _state => onTerminate()
    });

    /** 加个条件 active==true 才能真的 emitPress;?
     * 直接依赖于状态机切换的： active和emitPress是平行身份，onPress不直接取决于active。
     * Emit a press event if not disabled
     * @param e
     */
    function emitPress(e: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event
    ) {
        if (!disabled && onPress) {
            onPress(e);
        }
    }

    function emitLongPress(e: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event
    ) {
        if (!disabled && onLongPress) {
            onLongPress(e);
        }
    }

    /** 触摸或者鼠标点到了：  不是键盘模式。
     * Get our initial bounding box clientRect and set any delay
     * timers if necessary.
     * @param e 事件
     * @param delayPressMs 延迟触发
     * 底层的 useGestureResponder的 ｛ onGrant : } 回调本函数= 触摸启动了；
     * 但是：按键 "keydown" && e.which === SPACE 也能调用本函数的:
     * 默认延迟,触摸方式=delay 毫秒； 但是若isHoverEnabled()是鼠标点的非触摸发生的之后就是缺省=0毫秒的。
     */
    function onStart(e: ResponderEvent, delayPressMs = delay) {
        //异步发送=状态变更滞后。
        dispatch({ type: "RESPONDER_GRANT" });     //仅仅算一个启动点，还不能算 点按生效了。
        bounds.current = ref.current!.getBoundingClientRect();
        delayTimer.current =
            delayPressMs > 0
                ? window.setTimeout(afterDelay, delayPressMs, e)
                : undefined;
        //没有延时的就直接：
        if (delayPressMs === 0) {
            dispatch({ type: "PRESS_DETECTED" });
        }
        //【可能是长按的】长按 短按都保留：
        longDelayTimer.current = window.setTimeout(afterLongDelay, longPressDelay);
        //滚动情况，强制取消地
        bindScroll();
        // setShowHover(false);
    }

    //延迟发起的: 前面delay时间过后，hover切换；
    //前面延迟delay之后还能维持点击状态的，才可以触发点击生效。
    function afterDelay(e: ResponderEvent) {
        //旧的只有这一句：  dispatch({ type: "PRESS_DETECTED" });
        dispatch({ type: "PRESS_DETECTED" });
        //emitPress(e);
    }

    function afterLongDelay() {
        //检测到了 按住时间 超长的：
        dispatch({ type: "LONG_PRESS_DETECTED" });
        //应该等待到 RESPONDER_LONG_PRESSED_IN 状态,然后在onEnd()中: 才能触发执行onLongPress()。
        //emitLongPress();        //时间到了就能触发？？前面状态够苛刻了。
    }

    //正常点按结束允许 触发的：
    //这个才 真正触发 底层onRelease钩子，触摸结束，并且真正产生点击结果的， 执行按钮回调函数动作。
    function onEnd(
        e?: React.TouchEvent | React.MouseEvent | React.KeyboardEvent | Event
    ) {
        // consider unbinding the end event instead
        if (responder === "NOT_RESPONDER") {
            return;
        }
        //状态转移矩阵的：
        //旧版允许 responder === "RESPONDER_ACTIVE_IN" || 的时刻，也能被触发。
        if( e &&  ( responder === "RESPONDER_PRESSED_IN") ) {
            emitPress(e);
        }
        else if( e &&  ( responder === "RESPONDER_LONG_PRESSED_IN") ) {
            emitLongPress(e);
        }

        dispatch({ type: "RESPONDER_RELEASE" });

        //状态机事件触发前后都是"NOT_RESPONDER"的说明组件可能已经卸载了！
        //首先 RESPONDER_RELEASE: 后面又来了 RESPONDER_TERMINATED:
        //if(responder!=="ERROR" && !showHover){
       // if(!showHover){
       //      setShowHover(true);
       // }
       unbindScroll();
    }

    //非正常点按完结的， 被其它因素所取消的情况： onTerminate should be disambiguated from onRelease
    // because it should never 没有触发按钮回调函数动作的 trigger onPress events.
    function onTerminate() {
        if (responder === "NOT_RESPONDER") {
            return;
        }

        dispatch({ type: "RESPONDER_RELEASE" });
        // setShowHover(true);
        unbindScroll();
    }

    /**按了触摸同时摸着移动的？:移动距离很短的可以允许，太远的不能忽视。
     * Determine if the touch remains in the active bounds
     * @param e
     */
    function onTouchMove(e: any) {
        if(!movable){
            dispatch({ type: "RESPONDER_TERMINATED" });
            return;
        }

        if (responder === "NOT_RESPONDER" || responder === "ERROR") {
            return;
        }
        clearTimeout(longDelayTimer.current);

        const { clientX, clientY } = e.touches && e.touches[0] ? e.touches[0] : e;
        const withinBounds = isWithinActiveBounds(
            clientX,
            clientY,
            bounds.current!
        );
        //离开了我的可点击按钮矩形区域 ?。
        if (withinBounds) {
            dispatch({ type: "ENTER_PRESS_RECT" });
        } else {
            dispatch({ type: "LEAVE_PRESS_RECT" });
        }
    }


    /** 鼠标模式:
     * If our mouse leaves we terminate our responder,
     * even if our press remains down. This emulates
     * native mouse behaviour.
     * @param e
     */
    function onMouseLeave() {
        if (hover) {
            setHover(false);
        }
        // if (!showHover) {
        //     setShowHover(true);
        // }
        if (responder !== "NOT_RESPONDER") {
            terminateCurrentResponder();
        }
    }

    function onMouseEnter() {
        if (!hover) {
            setHover(true);
        }
    }


    /** 组件卸载了该怎么办？  后面useEffect(return () => {},[])实际是组件被卸载时刻执行。
     * Handle timer and disabled side-effects
     */
    React.useEffect(() => {
        return () => {
            //报错 unmounted, 针对组件被卸载情形：新版本直接dispatch就可以了，似乎React.useReducer内部自动避免 报错unmounted。
            //旧版本用，特殊状态来代表已经被卸载了。  state.current = "ERROR";
            dispatch( { type: "RESPONDER_TERMINATED" } );
            clearTimeout(delayTimer.current);
            clearTimeout(longDelayTimer.current);
            unbindScroll();
        };
    }, []);

    //外部参数disabled导致的
    React.useEffect(() => {
        if (disabled && responder !== "NOT_RESPONDER") {
            dispatch({ type: "RESPONDER_TERMINATED" });
            // setShowHover(true);
        }
    }, [disabled]);

    /** 键盘模式 直接就能触发！
     * 空格键  回车键。  Keyboard support ,必须是foucsed?
     *   onEnterDown -> onPress
     *   onSpaceUp -> onPress
     * Prevent default.
     * link: Don't prevent default
     */
    function onKey(e: React.KeyboardEvent) {
        if (e.type === "keydown" && e.key === " ") {
            onStart(null,0);
        } else if (e.type === "keydown" && e.key === "Enter") {
            onStart(null,0);
            //旧版是 一行的 emitPress(e);
        } else if (e.type === "keyup" && e.key === " ") {
            onEnd(e);
        }
        else if (e.type === "keyup" && e.key === "Enter") {
            onEnd(e);
        }
        else {
            return;
        }
        //停止冒泡，就不会传递给上级组件Div了
        e.stopPropagation();

        if (!(e.key === "Enter" && behavior === "link")) {
            //阻止缺省行为
            e.preventDefault();
        }
    }

    function isWithinActiveBounds(
        clientX: number,
        clientY: number,
        rect: ClientRect,
        expandPx: number = PRESS_EXPAND_PX
    ) {
        return (
            clientX > rect.left - expandPx &&
            clientY > rect.top - expandPx &&
            clientX < rect.right + expandPx &&
            clientY < rect.bottom + expandPx
        );
    }


    return {
        bind: {
            ...bind,
            onKeyUp: onKey,
            onKeyDown: onKey,
            onMouseEnter,
            onMouseLeave,
            ref
        },
        active: !disabled && active,
        hover: !disabled && isHoverEnabled() && hover
    };
}



/*
注意useGestureResponder({ 前面4个配置：xxxCapture优先； Startxxx优先; return true=启用。否则后面顺序的优先启用
export interface Callbacks {
      onStartShouldSetCapture?: CallbackQueryType;       按下一次调用
      onStartShouldSet?: CallbackQueryType;
      onMoveShouldSetCapture?: CallbackQueryType;       很多调用机会
      onMoveShouldSet?: CallbackQueryType;
* */
